package net.moonbiter.ebs.descriptors;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import net.moonbiter.OperationFailureException;
import net.moonbiter.ebs.validation.ValidationException;

/**
 * Descriptor that instruct how to instantiate
 * a value using parameters of a constructor. 
 * 
 * @author federico tomassetti
 */
public class InstantiatorDescriptor<INPUTS, DESCRIBED_TYPE> implements
		Descriptor<INPUTS, DESCRIBED_TYPE> {
	private Constructor<? extends DESCRIBED_TYPE> constructor;
	private Map<Integer, Descriptor<INPUTS, ?>> specificDescriptors;
	
	public Map<Integer, Descriptor<INPUTS, ?>> getSpecificDescriptors(){
		return specificDescriptors;
	}
	
	public int getNbOfConstructorParams(){
		return constructor.getParameterTypes().length;
	}
	
	public InstantiatorDescriptor(Constructor<? extends DESCRIBED_TYPE> constructor){
		this.constructor = constructor;
		this.specificDescriptors = new HashMap<Integer, Descriptor<INPUTS, ?>>();
	}
	
	public void setSpecificDescriptor(int index, Descriptor<INPUTS, ?> descriptor){
		if (index<0 || index>=constructor.getParameterTypes().length){
			throw new IndexOutOfBoundsException();
		}
		specificDescriptors.put(index, descriptor);
	}

	public DESCRIBED_TYPE produce(INPUTS inputs,
			DescriptorsSet<INPUTS> descriptorsSet)
			throws OperationFailureException, ValidationException {
		Object[] constructorParams = new Object[constructor.getParameterTypes().length];
		for (int i = 0; i < constructorParams.length; i++) {
			Descriptor<INPUTS,?> descriptor = null;
			if (specificDescriptors.containsKey(i)) {
				descriptor = specificDescriptors.get(i);
			} else {
				Class<?> paramClass = constructor.getParameterTypes()[i]; 
				descriptor = descriptorsSet.getDescriptor(paramClass);
			}
			constructorParams[i] = descriptor.produce(inputs, descriptorsSet);
						
			DESCRIBED_TYPE instance = null;
			try {
				instance = constructor.newInstance(constructorParams);
			} catch (IllegalArgumentException e) {
				throw new OperationFailureException("instantiation",e);
			} catch (InstantiationException e) {
				throw new OperationFailureException("instantiation",e);
			} catch (IllegalAccessException e) {
				throw new OperationFailureException("instantiation",e);
			} catch (InvocationTargetException e) {
				throw new OperationFailureException("instantiation",e);
			}
			return instance;
		}
		return null;
	}

}
